#include <QtCore/QCoreApplication>
#include <QPluginLoader>
#include <QDir>
#include <QFile>

#include "../plugin.h"
#include "../data/fileplugin.h"
#include "../data/reg3dvolumedata.h"
#include "../data/proteindata.h"
#include "../contour/molskin.h"
#include "../contree/contreeconstructor3d.h"
#include "../contree/augmentedctree.h"
#include "../contree/ctreecontourextractor.h"
#include "../math/timer.h"
#include "../mact/dctconstructor.h"
#include "../mact/dualgraph.h"
#include "../shape/sphere.h"
#include "../contour/mcconextractor.h"
#include "../sdf/geometryflow.h"
#include "../sdf/distancetransform.h"
#include "../sdf/pocketextractor.h"
#include "../sdf/pocketrefine.h"
#include "../getopts/getopts.h"

//#include <boost/config.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <utility>
//#include <boost/graph/adjacency_list.hpp>
//#include <boost/graph/connected_components.hpp>

using namespace PBVP;

FilePluginInterface* rawivPlugin;
FilePluginInterface* rawPlugin;
FilePluginInterface* rawncPlugin;
FilePluginInterface* pdbPlugin;
FilePluginInterface* pmeshPlugin;

void loadPlugins(QCoreApplication &a);

int main(int argc, char *argv[])
{

	QCoreApplication a(argc, argv);
	//return a.exec();
	loadPlugins(a);

	int dist = 35;
	float eps = 1.5;
	QString out;
	int np = 4;
	int nsmth = 0;
	int refine = 1;
	struct options opts[] = 
	{
		{1, "distance", "Marching distance", "d", 1},
		{2, "epsilon", "Isovalue of extracting pocket envelops", "e", 1},
		{3, "output", "Output file prefix", "o", 1},
		{4, "npockets", "Number of top pockets to be extracted", "p", 1}, 
		{5, "smoothing", "Number of times to smooth the pocket surface", "s", 1},
		{6, "refine", "Refine the extracted pocket", "r", 1},
		{0, NULL, NULL, NULL, 0}
	};
	char *args;
	int c;
	while( (c = getopts(argc, argv, opts, &args)) != 0) {
		switch(c) {
		// Special Case: Recognize options that we didn't set above.
        case -2:
			printf("Unknown Option: %s\n", args);
        break;
        // Special Case: getopts() can't allocate memory.
        case -1:
			printf("Unabled to allocate memory from getopts().\n");
        break;
        case 1:
			dist = atoi(args);
			break;
        case 2:
			eps = atof(args);
			break;
		case 3:
			out = args;
			break;
		case 4:
			np = atoi(args);
			break;
		case 5:
			nsmth = atoi(args);
			break;
		case 6:
			refine = atoi(args);
			break;
		default:
			break;
		}
		free(args);
	}

	Timer timer;
	timer.getCurrentTime();
	std::cout << "processing file " << argv[1] << std::endl;
	ProteinData* pProtein = dynamic_cast<ProteinData*>(pdbPlugin->loadFile(argv[1]));
	if(!pProtein)
	{
		std::cerr<<"Error loading file: "<<argv[1]<<std::endl;
		exit(0);
	}

	// compute the pocket volume data
	PocketExtractor *pPocketExtract= new PocketExtractor(dist, eps, nsmth);
	Reg3DVolumeData *pPocketVolume;
	MolSkin molSkin;
	GeometryData *pMolSurf = molSkin.constructMolSurface(pProtein->getAtoms());
	pPocketVolume = pPocketExtract->computePocketVolume(pMolSurf);
	
	// compute the contour tree to separate individual pocket envelopes
	AugmentedContourTree *acTree;
	ConTreeConstructorReg3D *constructCT = new ConTreeConstructorReg3D(pPocketVolume);
	acTree = constructCT->getAugmentedContourTree();
	delete constructCT;

	// extract the pockets from the volume data
	std::vector<GeometryData *> contours;
	CTreeContourExtractor *pConExtractor = new CTreeContourExtractor(pPocketVolume, acTree);
	//if inner side > isovalue, use setInnerRegion(1).
	pConExtractor->setInnerRegion(1);
	int nContour = pConExtractor->extractLargestContours(eps, contours, np);
	std::cout << nContour << " pocket extracted" << std::endl;
	delete pConExtractor;
	delete acTree;

	// print out the contour volumes to a file
	QString volfname = out + "_vol.txt";
	std::ofstream volout(volfname.toStdString().c_str());
	if(volout.is_open()) {
		for(int i = 0; i < nContour; i++) {
			Contour3DData *pCon = dynamic_cast<Contour3DData *>(contours[i]);
			volout << i+1 << "\t" << pCon->innerVolume() << "\t"<< pCon->area() << std::endl;
		}
	}
	volout.close();

	PocketRefine *pRefine = new PocketRefine(pMolSurf, pPocketVolume);
	std::vector<GeometryData *> rContours;
	while(!contours.empty()) {
		if (refine != 0)				// perform refinement
		{
			// Notice rContours saves pockets in the reversed order of their sizes
			rContours.push_back(pRefine->refinePocket(contours.back(), eps));
			//rContours.push_back(contours.back());
			delete contours.back();
			contours.pop_back();
		} else							// no refinement
		{
			rContours.push_back(contours.back());
			contours.pop_back();
		}
		
	}
	delete pRefine;
	delete pMolSurf;

	for(int i = 0; i < rContours.size(); i++) 
	{
		QString ind;
		ind.setNum(i);
		QString fname = out + "_" + ind + ".pmesh";
		pmeshPlugin->saveFile(rContours[rContours.size()-i-1], fname);
	}

	std::cout << "Elapsed time = " << timer.getElapsedTime() << std::endl;
	while (!rContours.empty()) 
	{
		delete rContours.back();  
		rContours.pop_back();
	}
	delete pPocketVolume;
	delete pPocketExtract;
	delete pProtein;
}

void loadPlugins(QCoreApplication &a)
{
	// load plugins
	QDir pluginsDir = QDir(a.applicationDirPath());
	std::cout << pluginsDir.absolutePath().toStdString() << std::endl;
	if (pluginsDir.dirName().toLower() == "debug"
		|| pluginsDir.dirName().toLower() == "release") {
			pluginsDir.cdUp();
	}
	// pluginsDir.cdUp();
	pluginsDir.cd("plugins");

	QStringList pluginEntries = pluginsDir.entryList(QDir::Files);

	// check additional potential plugin path if necessary
	if(pluginEntries.size() == 0) {
		pluginsDir = QDir(a.applicationDirPath());
		pluginsDir.cd("plugins");
		pluginEntries = pluginsDir.entryList(QDir::Files);
	}

	std::cout << "Loading plugins in dir" << pluginsDir.absolutePath().toStdString() << std::endl;

	foreach (QString fileName, pluginEntries) {
		if (fileName.contains("rawiv"))
		{
			QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
			QObject *plugin = loader.instance();
			if (plugin) {
				rawivPlugin = qobject_cast<FilePluginInterface *>(plugin);
				if (rawivPlugin) {
					rawivPlugin->initialize(NULL);
					std::cout << rawivPlugin->description().toStdString() << std::endl;
				}
			}
		} else if (fileName.contains("rawfile"))
		{
			QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
			QObject *plugin = loader.instance();
			if (plugin) {
				rawPlugin = qobject_cast<FilePluginInterface *>(plugin);
				if (rawPlugin) {
					rawPlugin->initialize(NULL);
					std::cout << rawPlugin->description().toStdString() << std::endl;
				}
			}
		} else if (fileName.contains("rawncfile"))
		{
			QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
			QObject *plugin = loader.instance();
			if (plugin)
			{
				rawncPlugin = qobject_cast<FilePluginInterface *>(plugin);
				if (rawncPlugin)
				{
					rawncPlugin->initialize(NULL);
					std::cout << rawncPlugin->description().toStdString() << std::endl;
				}
			}
		} else if (fileName.contains("pdbfile"))
		{
			QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
			QObject *plugin = loader.instance();
			if (plugin)
			{
				pdbPlugin = qobject_cast<FilePluginInterface *>(plugin);
				if (pdbPlugin)
				{
					pdbPlugin->initialize(NULL);
					std::cout << pdbPlugin->description().toStdString() << std::endl;
				}
			}
		} else if (fileName.contains("pmeshfile"))
		{
			QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
			QObject *plugin = loader.instance();
			if (plugin)
			{
				pmeshPlugin = qobject_cast<FilePluginInterface *>(plugin);
				if (pmeshPlugin)
				{
					pmeshPlugin->initialize(NULL);
					std::cout << pmeshPlugin->description().toStdString() << std::endl;
				}
			}
		}
	}
}